#include "byte.h"
#include "binary.h"
#include "logger.h"

#include "cowwork.h"
#include "cowservice.h"

extern string g_exepath;

static int get_request_buffer(RecvBlockQueue& recvqueue, const char* addrinfo, const char* inbuf, int inlen)
{
    CXmlBlock* block = new CXmlBlock(addrinfo);

    if(NULL == block) return 0;

    CXmlBuffer recvmsg(inbuf, inlen);

    int result = recvmsg.ToBlock(block);

    if(result <= 0) delete block;
    else recvqueue.Push(block);

    return result;
}

static int get_response_buffer(RecvBlockQueue& queue, CCowCmd* outcmd)
{
    static CLog* log = GET_LOG("_socket");

    RecvBlock* recvblock = NULL;

    if(0 != queue.Pop(recvblock)) return 0;

    CXmlBlock* root = (CXmlBlock*)(recvblock->GetNextChild());

    if(!outcmd->LoadFromNetPack(*root))
    {
        LOG_ERROR(log, "[outcmd:%s] LoadFromNetPack failed [%d:%s]\n", recvblock->GetOwnerName());

        DEL_SINGLE(recvblock); return -2;
    }

    DEL_SINGLE(recvblock);

    return 1;
}

THREAD_HANDLE(cow_work_thread, param)
{
    WorkThreadParam* work = (WorkThreadParam*)param;

    int workindex = work->_index;

    CCowWork* server = (CCowWork*)(work->_server);

    DEL_SINGLE(work);

    server->WorkThread(workindex);

    return 0;
}

/////////////////////////////////   CCowSyncClient      ///////////////////////////////////

CCowSyncClient::CCowSyncClient()
{
}

CCowSyncClient::~CCowSyncClient()
{
    RecvBlock* recvblock= NULL;

    while(0 == m_recvqueue.Pop(recvblock))
    {
        DEL_SINGLE(recvblock);
    }
}

int CCowSyncClient::OnRecvMsg(const char* inbuf, int inlen)
{
    return get_request_buffer(m_recvqueue, m_socketinfo._addrinfo, inbuf, inlen);
}

/////////////////////////////////   CCowClient      ///////////////////////////////////

CCowClient::CCowClient()
{
}

CCowClient::~CCowClient()
{
    RecvBlock* recvblock = NULL;

    while(0 == m_recvqueue.Pop(recvblock))
    {
        DEL_SINGLE(recvblock);
    }
}

bool CCowClient::GetOutCmd(CCowCmd* outcmd)
{
    static const int synctcpclient_recv_timeout = 30 * 1000;

    uint curtick = get_tick_count();

    while(curtick + synctcpclient_recv_timeout >= get_tick_count())
    {
        int result = get_response_buffer(m_recvqueue, outcmd);

        if(result < 0)
        {
            if(NULL != m_callbackfunc)
                m_callbackfunc(m_socketinfo._addrinfo);

            return false;
        }
        else if(result > 0)
        {
            return true;
        }
    }

    return false;
}

int CCowClient::OnRecvMsg(const char* inbuf, int inlen)
{
    return get_request_buffer(m_recvqueue, m_socketinfo._addrinfo, inbuf, inlen);
}

/////////////////////////////////   CCowServer      ///////////////////////////////////

CCowServer::CCowServer()
{
}

CCowServer::~CCowServer()
{
    RecvBlock* recvblock = NULL;

    while(0 == m_recvqueue.Pop(recvblock))
    {
        DEL_SINGLE(recvblock);
    }
}

int CCowServer::OnRecvMsg(const char* addrinfo, const char* inbuf, int inlen)
{
    return get_request_buffer(m_recvqueue, addrinfo, inbuf, inlen);
}

/////////////////////////////////   CCowWork      ///////////////////////////////////

CCowWork::CCowWork()
: m_isrun(false)
, m_isserver(false)
, m_client(NULL)
, m_workthreadcount(1)
, m_servers(NULL)
, m_mysqls(NULL)
, m_workthreads(NULL)
{
#ifdef _DEBUG
     AllocConsole();
#endif
}

CCowWork::~CCowWork()
{
#ifdef _DEBUG
    FreeConsole();
#endif

    sleep_s(1);

    Clean();
}

void CCowWork::Clean()
{
    if(NULL != m_client)
    {
        DEL_SINGLE(m_client);
    }

    if(NULL != m_syncclient)
    {
        DEL_SINGLE(m_syncclient);
    }

    if(NULL != m_workthreads)
    {
        for(int index = 0; index < m_workthreadcount; ++index)
        {
            DEL_SINGLE(m_workthreads[index]);
        }

        DEL_ARRAY(m_workthreads);
    }

    if(NULL != m_servers)
    {
        for(int index = 0; index < m_workthreadcount; ++index)
        {
            DEL_SINGLE(m_servers[index]);
        }

        DEL_ARRAY(m_servers);
    }

    if(NULL != m_mysqls)
    {
        for(int index = 0; index < m_workthreadcount; ++index)
        {
            DEL_SINGLE(m_mysqls[index]);
        }

        DEL_ARRAY(m_mysqls);
    }
}

bool CCowWork::Start(const char* configname)
{
    if(m_isrun) return false;

    m_isrun = true;

    if(g_exepath.empty()) parse_moudle_path();

    //>>>lqguo config

    char configpath[1024] = { 0 };

    sprintf(configpath, "%s/../config/%s", g_exepath.c_str(), configname);

    int configsize = file_size(configpath);

    if(configsize <= 0)
        m_isserver = false;
    else
        m_isserver = true;

    if(m_isserver)
    {
        char* configbuffer = new char[configsize];

        if(NULL == configbuffer) m_isserver = false;

        if(m_isserver && !file_read(configpath, configbuffer, configsize))
            m_isserver = false;

        if(m_isserver && !m_config.ParseConfig(configbuffer, configsize))
            m_isserver = false;
    
        DEL_ARRAY(configbuffer);
    }

    //>>>config end
    
    string worktype = "client";

    if(m_isserver)
        worktype = m_config.GetWorkType();

    if("cow" == worktype)
        return StartServer();
    else if("syncclient" == worktype)
        return StartSyncClient();
    else
        return StartClient();
}

/***********   CCowSyncClient      *************/

bool CCowWork::StartSyncClient()
{
    m_syncclient = new CCowSyncClient();

    if(NULL == m_syncclient) return false;

    string ip = m_config.GetWorkIp();

    if(ip.size() > 15 || ip.size() < 7) return false;

    ushort port = m_config.GetWorkPort();

    if(0 == port) return false;

    if(!m_syncclient->SetSocketInfo(ip.c_str(), port))
        return false;

    return true;
}

bool CCowWork::SyncWork(CCowCmd* incmd, CCowCmd* outcmd)
{
    if(NULL == m_syncclient) return false;

    static RecvBlockQueue& recvqueue = m_client->m_recvqueue;

    CXmlBuffer sendbuf;

    if(!incmd->ToNetPack(sendbuf))
        return false;

    if(!m_syncclient->SyncWork(sendbuf.GetByte(), sendbuf.GetSize()))
        return false;

    return get_response_buffer(m_syncclient->m_recvqueue, outcmd) > 0;
}

/***********   CCowClient      *************/

bool CCowWork::StartClient()
{
    m_client = new CCowClient();

    if(NULL == m_client) return false;

    string ip = m_config.GetWorkIp();

    if(ip.size() > 15 || ip.size() < 7) return false;

    ushort port = m_config.GetWorkPort();

    if(0 == port) return false;

    if(!m_client->StartSocket(ip.c_str(), port))
        return false;

    return true;
}

bool CCowWork::GetOutCmd(CCowCmd* outcmd)
{
    return m_client->GetOutCmd(outcmd);
}

void CCowWork::SetClientCallback(socket_callback_func func)
{
    if(NULL != m_client) m_client->SetCallbackFunc(func);
}

bool CCowWork::PushInCmd(CCowCmd* incmd)
{
    if(NULL == m_client) return false;

    CXmlBuffer* sendmsg = new CXmlBuffer(m_client->m_socketinfo._addrinfo);

    if(NULL == sendmsg) return false;

    if(!incmd->ToNetPack(*sendmsg))
    {
        delete sendmsg; return false;
    }

    m_client->OnSendMsg(sendmsg);

    return true;
}

/***********   CCowServer      *************/

bool CCowWork::StartServer()
{
    m_workthreadcount = m_config.GetWorkThreadCount();

    if(m_workthreadcount <= 0) return false;

    m_workthreads = new CThread*[m_workthreadcount];

    for(int index = 0; index < m_workthreadcount; ++index)
        m_workthreads[index] = NULL;

    m_servers = new CCowServer*[m_workthreadcount];

    for(int index = 0; index < m_workthreadcount; ++index)
        m_servers[index] = NULL;

    m_mysqls = new CCowMysql*[m_workthreadcount];

    for(int index = 0; index < m_workthreadcount; ++index)
        m_mysqls[index] = NULL;

    for(int index = 0; index < m_workthreadcount; ++index)
    {
        SqlInfo sqlinfo;

        if(!m_config.GetSqlInfo(sqlinfo))
            return false;

        m_mysqls[index] = new CCowMysql(sqlinfo);

        m_servers[index] = new CCowServer();

        m_workthreads[index] = new CThread();

        if(NULL == m_servers[index] || NULL == m_workthreads[index] || NULL == m_mysqls[index])
            return false;

        if(!m_mysqls[index]->Start())
            return false;

        ushort port = m_config.GetWorkPort(index+1);

        if(0 == port) return false;

        if(!m_servers[index]->StartSocket(port)) return false;

        WorkThreadParam* params = new WorkThreadParam();

        if(NULL == params) return false;

        params->_server = this; params->_index = index;

        if(!m_workthreads[index]->Start(cow_work_thread, params)) return false;
    }

    return true;
}

void CCowWork::WorkThread(int index)
{
    CXmlBlock* root = NULL;

    RecvBlock* recvblock = NULL;
    
    CXmlBuffer* sendbuf = NULL;

    CCowService* service = NULL;

    CLog* log = GET_LOG("_socket");

    CCowMysql* mysql = m_mysqls[index];

    CCowServer* server = m_servers[index];

    CCowSrvFactory& factory = sCowSrvFactory;

    RecvBlockQueue& recvqueue = m_servers[index]->m_recvqueue;

    while(m_isrun)
    {
        sleep_ms(1);

        if(0 != recvqueue.Pop(recvblock)) continue;

        root = (CXmlBlock*)(recvblock->GetNextChild());

        string method = ""; de_xml_attr(*root, "method", method);
        
        if(method.empty())
        {
            LOG_ERROR(log, "[%s] request xml no method\n", recvblock->GetOwnerName());

            server->RemoveSocketInfo(recvblock->GetOwnerName());

            DEL_SINGLE(recvblock); continue;
        }
        else
        {
            service = factory.Create(method.c_str());

            if(NULL == service)
            {
                LOG_ERROR(log, "[%s] get service pointer failed\n", recvblock->GetOwnerName());

                server->RemoveSocketInfo(recvblock->GetOwnerName());

                DEL_SINGLE(recvblock); continue;
            }

            sendbuf = new CXmlBuffer(recvblock->GetOwnerName());

            if(NULL == sendbuf)
            {
                LOG_ERROR(log, "[%s] get sendbuf pointer failed\n", recvblock->GetOwnerName());

                server->RemoveSocketInfo(recvblock->GetOwnerName());

                DEL_SINGLE(service); DEL_SINGLE(recvblock); continue;
            }

            service->Initialize(root, mysql);

            if(!service->ToNetPack(*sendbuf))
            {
                LOG_ERROR(log, "[%s] response ToNetPack failed\n", recvblock->GetOwnerName());

                server->RemoveSocketInfo(recvblock->GetOwnerName());
                
                DEL_SINGLE(service); DEL_SINGLE(recvblock); DEL_SINGLE(sendbuf); continue;
            }

            server->OnSendMsg(sendbuf); DEL_SINGLE(service); DEL_SINGLE(recvblock);
        }
    }
}
